package wci.frontend.c;

import static wci.frontend.Source.EOF;
import static wci.frontend.c.CErrorCode.INVALID_CHARACTER;
import wci.frontend.EofToken;
import wci.frontend.Scanner;
import wci.frontend.Source;
import wci.frontend.Token;
import wci.frontend.c.tokens.*;

/**
 * CScanner
 * 
 * Scans the source to extract tokens for the C language
 * 
 * @author Greg Drysdale
 */

public class CScanner extends Scanner
{
	/**
	 * simple constructor
	 * @param source source to be used with the scanner
	 */
	public CScanner(Source source)
	{
		super(source);
	}
	
	/**
	 * Extract and return the next C token from the source.
	 * @return the next Token
	 * @throws Exception if an error occurs
	 */
	protected Token extractToken()
		throws Exception
	{
		skipWhiteSpace();
		
		Token token = null;
		char currentChar = currentChar();
		
		if (currentChar == EOF)
		{
			token = new EofToken(source);
		}
    	else if (Character.isLetter(currentChar))
    	{
    		token = new CWordToken(source);
    	}
    	else if (Character.isDigit(currentChar) || (currentChar == '.' && Character.isDigit(peekChar())))
    	{
    		token = new CNumberToken(source);
    	}
    	else if (currentChar == '\"')
    	{
    		token = new CStringToken(source);
    	}
    	else if (currentChar == '\'')
    	{
    		token = new CCharacterToken(source);
    	}
    	else if (CTokenType.SPECIAL_SYMBOLS
    			.containsKey(Character.toString(currentChar)))
    	{
    		token = new CSpecialSymbolToken(source);
    	}
    	else {
    		token = new CErrorToken(source, INVALID_CHARACTER,
                                 Character.toString(currentChar));
    		nextChar();  // consume character
    	}

		return token;
	}
	
	
	/**
	 * Skip whitespace and comments by consuming them
	 * @throws Exception
	 */
	private void skipWhiteSpace()
		throws Exception
	{
		char currentChar = currentChar();

        while (Character.isWhitespace(currentChar) || (currentChar == '/'))
        {

            // Start of a comment?
            if (currentChar == '/' && (peekChar() == '*' || peekChar() == '/'))
            {
            	currentChar = nextChar();
            	
            	// one-line comment
            	if (currentChar == '/')
            	{
            		do {
            			currentChar = nextChar();
            		} while ((currentChar != '\n') && (currentChar != EOF));
            	}
            	// block comment
            	else
            	{
            		nextChar(); //consume the *
            		
            		boolean starSeen = false;
            		do {
            			currentChar = nextChar();
            			
            			// comment block closed
            			if (starSeen && currentChar == '/')
            			{
            				currentChar = nextChar(); // consume /
            				break; // continue checking for whitespace
            			}
            			
            			// denotes possible beginning of comment block closure.
            			if (currentChar == '*')
            				starSeen = true;
            			else
            				starSeen = false;
            			
            		} while (currentChar != EOF);
            	}
            }
            // Not a comment.
            else
            {
            	if (Character.isWhitespace(currentChar))
            	{
            		currentChar = nextChar();  // consume whitespace character
            	}
            	else
            	{
            		// it was a '/' but not the start of a comment. we should break
            		// out and let the scanner continue
            		return;
            	}
            }
            
        }		
	}
}